﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Text;
using System.IO;
using System.Drawing;
using T3K.SketchEditor.Plugins;

namespace T3K.SketchEditor.Core {

    public delegate void CommandHandler(object caller, CommandArgs args);
    public delegate void EditorEventHandler (object sender, EditorEventArgs args);

    public class CommandArgs : EventArgs {

        private bool _handled;
        private CommandReturn _return;
        private Dictionary<string, object> _params;

        public CommandReturn Return {
            get { return _return; }
            set { _return = value; }
        }

        public bool Handled {
            get { return _handled; }
            set { _handled = value; }
        }

        public object this[string key] {
            get {
                if (_params.ContainsKey(key.ToLower()))
                    return _params[key.ToLower()];
                else
                    throw new ArgumentException(
                        string.Format("Parameter '{0}' not found", key)
                    );
            }
            set {
                if (!_params.ContainsKey(key.ToLower()))
                    _params.Add(key.ToLower(), value);
                else
                    _params[key.ToLower()] = value;
            }
        }

        public void AddRange(Dictionary<string, object> dict) {
            foreach (string key in dict.Keys)
                this[key] = dict[key];
        }

        public bool ContainsArg(string key, Type type) {
            return (_params.ContainsKey(key.ToLower()) && _params[key.ToLower()].GetType() == type);
        }

        public bool ContainsArg(string key) {
            return _params.ContainsKey(key.ToLower());
        }

        public CommandArgs(Dictionary<string, object> parameters) {
            this._handled = false;
            this._params = parameters;
            this._return = CommandReturn.Empty;
        }

        public CommandArgs(string name, object value) {
            this._handled = false;
            this._params = new Dictionary<string, object>();
            this._return = CommandReturn.Empty;
            this._params.Add(name.ToLower(), value);
        }

        public CommandArgs() {
            this._handled = false;
            this._params = new Dictionary<string, object>();
            this._return = CommandReturn.Empty;
        }

    }

    public class EditorEventArgs : CommandArgs {

        private string _eventType;

        public string EventType {
          get { return _eventType; }
          set { _eventType = value; }
        }

        public EditorEventArgs(string eventType) {
            this._eventType = eventType;
        }
    }

    public class CommandReturn {
        object _data;

        public object Data {
            get { return _data; }
            set { _data = value; }
        }

        public CommandReturn(object data) {
            _data = data;
        }

        public static CommandReturn Empty {
            get { return new CommandReturn(null); }
        }

        public override string ToString() {
            if (_data == null)
                return "NULL";
            else
                return _data.ToString();
        }
    }

    [AttributeUsage(AttributeTargets.Method)]
    public class CommandAttribute : Attribute {

        private string _command;
        private string _desc;
        private string _paramsDesc;

        public CommandAttribute(string command) {
            this._command = command;
        }

        public string Command {
            get { return _command; }
            set { _command = value; }
        }

        public string Desc {
            get { return _desc; }
            set { _desc = value; }
        }

        public string ParamsDesc {
            get { return _paramsDesc; }
            set { _paramsDesc = value; }
        }

    }

    public delegate object VariableCallHandler ();

    public class EditorVariable {
        private object _value;

        public object Value {
            get { return GetValue(); }
            set { _value = value; }
        }

        public EditorVariable(object value) {
            this._value = value;
        }

        public override string ToString() {
            if (_value != null)
                return _value.ToString();
            else
                return string.Empty;
        }

        public object GetValue() {
            Delegate method = _value as Delegate;
            if (method != null) {
                MethodInfo mInfo = method.Method;
                ParameterInfo[] pInfo = mInfo.GetParameters();
                if (pInfo == null || pInfo.Length == 0)
                    return method.DynamicInvoke(null).ToString();
                else
                    return method; //someone invoke me! :P
            }
            return _value;
        }

        public static EditorVariable FromCallHandler(VariableCallHandler callBack) {
            return new EditorVariable(callBack);
        }

    }

    public class EditorManager {

        private static EditorManager _instance;
        private static object _instaceLocker = new object();

        private Dictionary<string, CommandHandler> _commands;
        private Dictionary<string, EditorVariable> _variables;
        private Dictionary<string, PluginBase> _plugins;
        private Dictionary<string, Image> _images;
        private Dictionary<string, List<string>> _events;

        public event EditorEventHandler EditorEvent;

        private EditorManager() {
            _commands = new Dictionary<string, CommandHandler>();
            _variables = new Dictionary<string, EditorVariable>();
            _plugins = new Dictionary<string, PluginBase>();
            _images = new Dictionary<string, Image>();
            _events = new Dictionary<string, List<string>>();

            GetImages(new DirectoryInfo("Images"), string.Empty);

        }

        private void GetImages(DirectoryInfo dInfo, string baseName) {
            if (dInfo.Exists) {
                foreach (FileInfo fInfo in dInfo.GetFiles()) {
                    switch (fInfo.Extension.ToLower()) {
                        case ".png":
                        case ".gif":
                        case ".jpg":
                        case ".jpeg":
                            try {
                                Image img = Image.FromFile(fInfo.FullName);
                                string name = string.Format(
                                    "{0}{1}{2}",
                                    baseName,
                                    (   
                                        baseName != null 
                                        && baseName != string.Empty
                                        && !baseName.EndsWith("/")
                                    ) ? "/" : "",
                                    fInfo.Name
                                );
                                if (!_images.ContainsKey(name.ToLower()))
                                    _images.Add(name.ToLower(), img);
                            } catch { }
                            break;
                    }
                }

                foreach (DirectoryInfo subDInfo in dInfo.GetDirectories()) {
                    string name = string.Format(
                        "{0}{1}{2}",
                        baseName,
                        (
                            baseName != null
                            && baseName != string.Empty
                            && !baseName.EndsWith("/")
                        ) ? "/" : "",
                        subDInfo.Name
                    );
                    GetImages(subDInfo, name.ToLower());

                }
            }
        }

        public void InitPlugins() {
            Assembly assembly = Assembly.GetExecutingAssembly();
            foreach (Type t in assembly.GetTypes()) {
                Type baseType = t.BaseType;

                while (baseType != typeof(PluginBase) && baseType != typeof(object))
                    baseType = baseType.BaseType;

                if (baseType == typeof(PluginBase)) {
                    PluginBase plugin = (PluginBase)Activator.CreateInstance(t, true);
                    plugin.Init();
                    if (!_plugins.ContainsKey(plugin.PluginName))
                        _plugins.Add(plugin.PluginName, plugin);
                }
            }
        }

        public static EditorManager Instance {
            get {
                if (_instance == null) {
                    lock (_instaceLocker) {
                        if (_instance == null) {
                            _instance = new EditorManager();
                        }
                    }
                }
                return _instance;
            }
        }

        public Image GetImage(string name) {
            if (_images.ContainsKey(name.ToLower()))
                return _images[name.ToLower()];
            else
                return null;
        }

        #region Events Registration

        protected void OnEditorEvent(object sender, EditorEventArgs args) {
            if (EditorEvent != null)
                EditorEvent(sender, args);
        }

        public void Register_EventHandler(PluginBase plugin, string eventType) {
            if (!_events.ContainsKey(plugin.PluginName))
                _events.Add(plugin.PluginName, new List<string>());

            if (!_events[plugin.PluginName].Contains(eventType))
                _events[plugin.PluginName].Add(eventType);
        }

        public void Unregister_EventHandler(PluginBase plugin, string eventType) {
            if (_events.ContainsKey(plugin.PluginName) && _events[plugin.PluginName].Contains(eventType))
                _events[plugin.PluginName].Remove(eventType);
        }

        public void FireEvent(object sender, EditorEventArgs args) {

            foreach (string key in _events.Keys) {
                if (_events[key].Contains(args.EventType)) {
                    if (_plugins.ContainsKey(key))
                        _plugins[key].EditorEventHandler(sender, args);
                }
            }

            OnEditorEvent(sender, args);
        }

        #endregion

        #region Variables
        private object _variableLocker = new object();
        public void RegisterVariable(string name, EditorVariable variable) {
            if (!_variables.ContainsKey(name)) {
                lock (_variableLocker) {
                    _variables.Add(name, variable);
                }
            } else
                throw new ArgumentException(string.Format("Variable '{0}' already registered", name), "name");
        }

        public void UnregisterVariable(string name) {
            if (_variables.ContainsKey(name)) {
                lock (_variableLocker) {
                    _variables.Remove(name);
                }
            }
        }

        private static Regex reEnvArgs = new Regex("\\$\\$\\(\\%([a-zA-Z0-9_]+)\\%\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private static Regex reArgs = new Regex("\\$\\(([a-zA-Z0-9_]+)\\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        public Dictionary<string, object> ExpandVariables(string parameters) {

            Dictionary<string, object> returnSet = new Dictionary<string, object>();

            Match m1 = reArgs.Match(parameters);
            while (m1.Success) {
                if (m1.Groups.Count > 0) {
                    string key = m1.Groups[1].Value;
                    object var = GetVariable(key);

                    if (!returnSet.ContainsKey(key))
                        returnSet.Add(key, var);
                    else
                        returnSet[key] = var;
                }

                m1 = m1.NextMatch();
            }

            Match m2 = reArgs.Match(parameters);
            while (m2.Success) {
                if (m2.Groups.Count > 0) {
                    string key = m2.Groups[1].Value;
                    object var = Environment.GetEnvironmentVariable(key);

                    if (!returnSet.ContainsKey(key))
                        returnSet.Add(key, var);
                    else
                        returnSet[key] = var;
                }

                m2 = m2.NextMatch();
            }

            return returnSet;
        }


        public object GetVariable(string name) {
            if (_variables.ContainsKey(name) && _variables[name] != null)
                return _variables[name].Value;
            else
                return null;
        }

        public void SetVariable(string name, object value) {
            if (_variables.ContainsKey(name))
                _variables[name].Value = value;
            else _variables.Add(name, new EditorVariable(value));
        }
        #endregion

        #region Commands
        private object _commandLocker = new object();
        public void RegisterCommand(string command, CommandHandler handler) {
            if (!_commands.ContainsKey(command)) {
                _commands.Add(command, handler);
            } else
                _commands[command] += handler;
        }

        public void UnregisterCommand(string command, CommandHandler handler) {
            if (_commands.ContainsKey(command)) {
                
                    _commands[command] -= handler;
                
            }
        }

        #region void
        //public void CheckCommandParameters(CommandHandler command, CommandArgs args) {

        //    if (command == null) return;

        //    CommandAttribute cAttr = null;

        //    foreach (object attr in command.Method.GetCustomAttributes(true)) {
        //        cAttr = attr as CommandAttribute;
        //        if (cAttr != null) {
        //            if (!string.IsNullOrEmpty(cAttr.ParamsDesc)) {
        //                string[] tokA = cAttr.ParamsDesc.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
        //                for (int p = 0; p < tokA.Length; p++) {
        //                    string paramDef = tokA[p].Trim().Split(new char[] {'<'}, StringSplitOptions.RemoveEmptyEntries)[0];
        //                    string[] tokB = paramDef.Split(new string[] {"as"}, StringSplitOptions.RemoveEmptyEntries);
        //                    if (tokB.Length == 2) {
        //                        string paramName = tokB[0].Trim();
        //                        string paramType = tokB[1].Trim();
        //                        bool optional = false;

        //                        if (paramName[0] == '&') {
        //                            optional = true;
        //                            paramName = paramName.Substring(1);
        //                        }

        //                        if (args.Parameters.Length <= p) {
        //                            if (!optional) {
        //                                throw new ArgumentException(
        //                                    string.Format(
        //                                        "The method {0} expects the {1}{2} parameter to be '{3} as {4}'",
        //                                        cAttr.Command,
        //                                        p + 1, (p == 0) ? "st" : "nd",
        //                                        paramName, paramType)
        //                                );
        //                            }
        //                        } else {

        //                            bool ok = false;

        //                            Type actualType = args.Parameters[p].GetType();
                                    

        //                            do {

        //                                string actualTypeName = actualType.Name;

        //                                if (actualType.IsGenericType) {
        //                                    if (!actualType.IsGenericTypeDefinition)
        //                                        actualType = actualType.GetGenericTypeDefinition();

        //                                    actualTypeName = actualType.Name.Split('`', '\'')[0];
        //                                }

        //                                ok |= actualTypeName.ToUpper().EndsWith(paramType.ToUpper());

        //                                if (ok) break;

        //                                actualType = actualType.BaseType;

        //                            } while (actualType != typeof(object));

        //                            if (!ok) {
        //                                throw new ArgumentException(
        //                                    string.Format(
        //                                        "The method {0} expects the {1}{2} parameter to be '{3} as {4}'",
        //                                        cAttr.Command,
        //                                        p + 1, (p == 0) ? "st" : "nd",
        //                                        paramName, paramType)
        //                                );
        //                            }
        //                        }
        //                    }
        //                }
        //            }
        //            break;
        //        }
        //    }
        //}
        #endregion

        public IAsyncResult BeginInvokeCommand(object caller, string command, CommandArgs args, AsyncCallback callback) {
            try {
                if (_commands.ContainsKey(command)) {
                    if (_commands[command] != null) {
                        return _commands[command].BeginInvoke(caller, args, callback, null);
                    }
                } else
                    ReportInvalidCommand(command);
            } catch (Exception ex) {
                if (AsDebugOutput())
                    ReportException(ex);
                else
                    throw;
            }

            return null;
        }

        public void InvokeCommand(object caller, string command) {
            InvokeCommand(caller, command, new CommandArgs());
        }

        public void InvokeCommand(object caller, string command, CommandArgs args) {
            try {
                if (_commands.ContainsKey(command)) {
                    if (_commands[command] != null) {
                        _commands[command].Invoke(caller, args);
                    }
                } else
                    ReportInvalidCommand(command);
            } catch (Exception ex) {
                if (AsDebugOutput())
                    ReportException(ex);
                else
                    throw;
            }
        }

        public void InvokeCommand(object caller, string command, string parameters) {
            try {
                if (_commands.ContainsKey(command)) {
                    if (_commands[command] != null) {
                        CommandArgs args = new CommandArgs(this.ExpandVariables(parameters));
                        _commands[command].Invoke(caller, args);
                    }
                } else
                    ReportInvalidCommand(command);
            } catch (Exception ex) {
                if (AsDebugOutput())
                    ReportException(ex);
                else
                    throw;
            }
        }

        private bool AsDebugOutput() {
            return _commands.ContainsKey("Main.Debug.WriteLine");
        }

        private void ReportException(Exception ex) {
            if (_commands.ContainsKey("Main.Debug.WriteLine")) {

                string message = string.Empty;
                ArgumentException aEx = ex as ArgumentException;
                if (aEx != null)
                    message = aEx.Message;
                else
                    message = ex.ToString();

                _commands["Main.Debug.WriteLine"].Invoke(this, new CommandArgs(
                    "Out", string.Format("Exception:\n {0}", message)));
                
            }
        }

        private void ReportInvalidCommand(string command) {
            if (_commands.ContainsKey("Main.Debug.WriteLine")) {
                _commands["Main.Debug.WriteLine"].Invoke(this, new CommandArgs(
                    "Out", string.Format("'{0}' Command not found!", command)));                
            }
        }


        public void UnregisterCommands(object obj, string baseName) {
            Type instanceType = obj.GetType();

            foreach (MethodInfo mInfo in instanceType.GetMethods()) {
                foreach (object mAttr in mInfo.GetCustomAttributes(true)) {
                    CommandAttribute formCommandAttr = mAttr as CommandAttribute;
                    if (formCommandAttr != null) {
                        this.UnregisterCommand(
                            string.Format("{0}.{1}", baseName, formCommandAttr.Command),
                            (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), obj, mInfo, true));
                    }
                }
            }
        }

        public void RegisterCommands(object obj, string baseName) {

            Type instanceType = obj.GetType();

            foreach (MethodInfo mInfo in instanceType.GetMethods()) {
                foreach (object mAttr in mInfo.GetCustomAttributes(true)) {
                    CommandAttribute formCommandAttr = mAttr as CommandAttribute;
                    if (formCommandAttr != null) {
                        this.RegisterCommand(
                            string.Format("{0}.{1}", baseName, formCommandAttr.Command),
                            (CommandHandler)Delegate.CreateDelegate(typeof(CommandHandler), obj, mInfo, true));

                        
                    }
                }
            }
        }

        public void MainMenu_CallBack(object sender, EventArgs args) {

            ToolStripItem item = sender as ToolStripItem;
            if (item != null) {
                string commandDef = item.Tag as string;
                string command = null;
                string parameters = null;
                if (commandDef != null) {
                    string[] commandTokens = commandDef.Split(new char[] { '/' }, 2, StringSplitOptions.RemoveEmptyEntries);
                    if (commandTokens.Length > 0)
                        command = commandTokens[0];
                    if (commandTokens.Length == 2)
                        parameters = commandTokens[1];

                    if (command != null) {
                        if (parameters != null) {
                            this.InvokeCommand(item, command, parameters);
                        } else {
                            this.InvokeCommand(item, command, string.Empty);
                        }
                    }
                }

            }

        }


        #endregion

        public static string Title {
            get {
                object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
                if (attributes.Length > 0) {
                    AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0];
                    if (!String.IsNullOrEmpty(titleAttribute.Title))
                        return titleAttribute.Title;
                }

                // If there was no Title attribute, or if the Title attribute was the empty string, return the .exe name
                return Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase);
            }
        }

        public static string ProjectsPath {
            get {

                string myDocuments = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                string EditorFolder = Path.Combine(myDocuments, "Sketch Editor");
                string SketchsFolder = Path.Combine(EditorFolder, "Sketchs");

                if (!Directory.Exists(EditorFolder))
                    Directory.CreateDirectory(EditorFolder);

                if (!Directory.Exists(SketchsFolder))
                    Directory.CreateDirectory(SketchsFolder);

                return SketchsFolder;
            }
        }

    }

}
